﻿//-----------------------------------------------------------------------
// <copyright file="Matrix2D.cs" company="Nathan Miller">
// Copyright (c) Nathan Miller.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// </copyright>
//-----------------------------------------------------------------------
namespace TileTool.Common.Math
{
    using System;

    /*
     * 0 2 4
     * 1 3 5
     */
    
    /// <summary>
    /// 2x3 matrix class stored in column major format.
    /// 
    /// Assumed to be an orthogonal matrix.
    /// </summary>
    public struct Matrix2D
    {
        #region Fields - Public

        /// <summary>
        /// Matrix entry 0.
        /// </summary>
        public float mat0;

        /// <summary>
        /// Matrix entry 1.
        /// </summary>
        public float mat1;

        /// <summary>
        /// Matrix entry 2.
        /// </summary>
        public float mat2;

        /// <summary>
        /// Matrix entry 3.
        /// </summary>
        public float mat3;

        /// <summary>
        /// Matrix entry 4.
        /// </summary>
        public float mat4;

        /// <summary>
        /// Matrix entry 5.
        /// </summary>
        public float mat5;

        #endregion

        #region Fields - Private

        /// <summary>
        /// Internal identity matrix.
        /// </summary>
        private static Matrix2D identity = new Matrix2D()
        {
            mat0 = 1.0f,
            mat1 = 0.0f,

            mat2 = 0.0f,
            mat3 = 1.0f,

            mat4 = 0.0f,
            mat5 = 0.0f
        };

        #endregion

        #region Constructor

        /// <summary>
        /// Matrix constructor.
        /// </summary>
        /// <param name="src">Source matrix.</param>
        public Matrix2D(Matrix2D src)
        {
            this.mat0 = src.mat0;
            this.mat1 = src.mat1;

            this.mat2 = src.mat2;
            this.mat3 = src.mat3;

            this.mat4 = src.mat4;
            this.mat5 = src.mat5;
        }

        #endregion

        #region Properties - Static

        /// <summary>
        /// Gets the identity matrix.
        /// </summary>
        public static Matrix2D Identity
        {
            get
            {
                return identity;
            }
        }

        #endregion

        #region Properties - Public

        /// <summary>
        /// Gets or sets the position component of the matrix.
        /// </summary>
        public Vector2D Pos
        {
            get
            {
                return new Vector2D(this.mat4, this.mat5);
            }

            set
            {
                this.mat4 = value.x;
                this.mat5 = value.y;
            }
        }

        /// <summary>
        /// Gets the X axis of the matrix.
        /// </summary>
        public Vector2D X
        {
            get
            {
                return new Vector2D(this.mat0, this.mat1);
            }
        }

        /// <summary>
        /// Gets the Y axis of the matrix.
        /// </summary>
        public Vector2D Y
        {
            get
            {
                return new Vector2D(this.mat2, this.mat3);
            }
        }

        #endregion

        #region Methods - Public static

        /// <summary>
        /// Attempt to parse a matrix from the passed string.
        /// </summary>
        /// <param name="value">String to parse a matrix from.</param>
        /// <param name="matrix">Destination matrix.</param>
        /// <returns>Returns true if the matrix was parsed successfully.</returns>
        public static bool TryParse(string value, out Matrix2D matrix)
        {
            matrix = Matrix2D.Identity;

            if (string.IsNullOrEmpty(value))
            {
                return false;
            }

            string[] parts = value.Split(' ');
            if (parts == null || parts.Length != 6)
            {
                return false;
            }

            bool success = true;

            success &= float.TryParse(parts[0], out matrix.mat0);
            success &= float.TryParse(parts[1], out matrix.mat1);
            success &= float.TryParse(parts[2], out matrix.mat2);
            success &= float.TryParse(parts[3], out matrix.mat3);
            success &= float.TryParse(parts[4], out matrix.mat4);
            success &= float.TryParse(parts[5], out matrix.mat5);

            return success;
        }

        #endregion

        #region Operator overloads

        /// <summary>
        /// Matrix multiply overload.
        /// </summary>
        /// <param name="lhs">Left hand side matrix.</param>
        /// <param name="rhs">Right hand side matrix.</param>
        /// <returns>Returns lhs * rhs.</returns>
        public static Matrix2D operator *(Matrix2D lhs, Matrix2D rhs)
        {
            return lhs.Multiply(rhs);
        }

        /// <summary>
        /// Matrix multiply overload.
        /// </summary>
        /// <param name="lhs">Left hand side matrix.</param>
        /// <param name="rhs">Right hand side vector.</param>
        /// <returns>Returns lhs * rhs.</returns>
        public static Vector2D operator *(Matrix2D lhs, Vector2D rhs)
        {
            return lhs.Multiply(rhs);
        }

        #endregion

        #region Methods - Public

        /// <summary>
        /// Set the matrix to identity.
        /// </summary>
        public void SetIdentity()
        {
            this.mat0 = 1.0f;
            this.mat1 = 0.0f;

            this.mat2 = 0.0f;
            this.mat3 = 1.0f;

            this.mat4 = 0.0f;
            this.mat5 = 0.0f;
        }
  
        /// <summary>
        /// Build a rotation matrix of angleRads radians.
        /// </summary>
        /// <param name="angleRads">Rotation amount in radians.</param>
        public void BuildRotation(float angleRads)
        {
            float cosRes = (float)Math.Cos(angleRads);
            float sinRes = (float)Math.Sin(angleRads);

            this.SetIdentity();

            this.mat0 = cosRes;
            this.mat1 = sinRes;
            this.mat2 = -sinRes;
            this.mat3 = cosRes;
        }

        /// <summary>
        /// Build a translation matrix with the passed amount.
        /// </summary>
        /// <param name="pos">Translation amount.</param>
        public void BuildTranslate(Vector2D pos)
        {
            this.BuildTranslate(pos.x, pos.y);
        }

        /// <summary>
        /// Build a translation matrix with the passed amount.
        /// </summary>
        /// <param name="x">X translation amount.</param>
        /// <param name="y">Y translation amount.</param>
        public void BuildTranslate(float x, float y)
        {
            this.SetIdentity();

            this.mat4 = x;
            this.mat5 = y;
        }
  
        /// <summary>
        /// Multiply this matrix by the passed matrix.
        /// </summary>
        /// <param name="rhs">Right hand side matrix.</param>
        /// <returns>Returns the product of this and the rhs matrix.</returns>
        public Matrix2D Multiply(Matrix2D rhs)
        {
            Matrix2D res = new Matrix2D();

            res.mat0 = (this.mat0 * rhs.mat0) + (this.mat2 * rhs.mat1);
            res.mat1 = (this.mat1 * rhs.mat0) + (this.mat3 * rhs.mat1);

            res.mat2 = (this.mat0 * rhs.mat2) + (this.mat2 * rhs.mat3);
            res.mat3 = (this.mat1 * rhs.mat2) + (this.mat3 * rhs.mat3);

            res.mat4 = (this.mat0 * rhs.mat4) + (this.mat2 * rhs.mat5) + this.mat4;
            res.mat5 = (this.mat1 * rhs.mat4) + (this.mat3 * rhs.mat5) + this.mat5;

            return res;  
        }
  
        /// <summary>
        /// Multiply the passed vector by the matrix and return the result.
        /// </summary>
        /// <param name="pt">Vector to multiply.</param>
        /// <returns>Returns the product of the vector and this matrix.</returns>
        public Vector2D Multiply(Vector2D pt)
        {
            Vector2D res = new Vector2D();

            res.x = (pt.x * this.mat0) + (pt.y * this.mat2) + this.mat4;
            res.y = (pt.x * this.mat1) + (pt.y * this.mat3) + this.mat5;
    
            return res;
        }

        /// <summary>
        /// Invert the passed matrix and return it.
        /// </summary>
        /// <returns>Returns the inverse of this matrix.</returns>
        public Matrix2D Invert()
        {
            Matrix2D rotInv = new Matrix2D();

            rotInv.mat0 = this.mat0;
            rotInv.mat3 = this.mat3;

            rotInv.mat1 = this.mat2;
            rotInv.mat2 = this.mat1;

            Matrix2D transInv = new Matrix2D();

            transInv.BuildTranslate(-this.mat4, -this.mat5);

            return rotInv * transInv;
        }

        /// <summary>
        /// Gets a new matrix which contains only the rotation part of
        /// this matrix.
        /// </summary>
        /// <returns>Returns a new matrix which contains only the rotation
        /// partial of this matrix.</returns>
        public Matrix2D RotationOnly()
        {
            Matrix2D res = new Matrix2D(this);

            res.Pos = Vector2D.Zero;

            return res;
        }
        
        /// <summary>
        /// Convert object to string.
        /// </summary>
        /// <returns>Returns a string representation of the object.</returns>
        public override string ToString()
        {
            return string.Format(
                           "{0} {1} {2} {3} {4} {5}",
                           this.mat0,
                           this.mat1,
                           this.mat2,
                           this.mat3,
                           this.mat4,
                           this.mat5);
        }

        #endregion
    }
}
